#include "src/gui/qt/graphicswidget.h"

GraphicsWidget::GraphicsWidget(QWidget *parent) :
    QGLWidget(parent)
{
  QGLFormat form = format();
  form.setAccum(true);
  setFormat(form);
  setMouseTracking(true);

  makeCurrent();
  distances_[0] = 0.5; distances_[1] = 100;
  perspective_ = 60;
  cameraCoord = Vector3(0, 5, 1);
  compAngles(Vector3(0.0, 0.0, 0.0));
  cameraPsy = 0.0;

  lastMousePos = QPoint(0, 0);
  viewMode = MoleculeCentered;
  isMoving = lowPerfomance = false;
  spd = 1.0;
  antialising = 4;

  quadricObject = gluNewQuadric();
  gluQuadricNormals(quadricObject, GLU_SMOOTH);
  overallLitList = ( overallUnlitList = (overallLitListFast = ( overallUnlitListFast = glGenLists(4) ) +1) +1 ) +1;

  setFocusPolicy(Qt::WheelFocus);
}

void GraphicsWidget::paintGL()
{
  // finish later
  const float jitterValues[8][8][2] =
  {
    { {0, 0},           {1, 1},           {2,2},     {3,3},     {4,4},     {5,5},     {6,6},    {7,7} },
    { {0.25, 0.75},     {0.75, 0.25},     {2,2},     {3,3},     {4,4},     {5,5},     {6,6},    {7,7} },
    { {0.5033922635, 0.8317967229},     {0.7806016275, 0.2504380877},     {0.2261828938, 0.4131553612},     {3,3},     {4,4},     {5,5},     {6,6},    {7,7} },
    { {0.375, 0.25},     {0.125, 0.75},     {0.875, 0.25},     {0.625, 0.75},     {4,4},     {5,5},     {6,6},    {7,7} },
    { {0.5, 0.5},     {0.3, 0.1},     {0.7,0.9},     {0.9,0.3},     {0.1,0.7},     {5,5},     {6,6},    {7,7} },
    { {0, 0},     {1, 1},     {2,2},     {3,3},     {4,4},     {5,5},     {6,6},    {7,7} },
    { {0.4646464646, 0.4646464646}, {0.1313131313, 0.7979797979}, {0.5353535353, 0.8686868686}, {0.8686868686, 0.5353535353}, {0.7979797979, 0.1313131313}, {0.2020202020, 0.2020202020},    {7,7},    {8,8} },
    { {0.5625, 0.4375}, {0.0625, 0.9375}, {0.3125, 0.6875}, {0.6875, 0.8125}, {0.8125, 0.1875}, {0.9375, 0.5625}, {0.4375, 0.0625}, {0.1875, 0.3125} }
  } ;

  glClear(GL_ACCUM_BUFFER_BIT);
  for (int jitter = 0; jitter < antialising; ++jitter)
  {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    accPerspective(perspective_, (GLdouble) viewport_[0] / (GLdouble) viewport_[1], 0.5, 100.0, jitterValues[antialising - 1][jitter][0], jitterValues[antialising - 1][jitter][1], 0.0, 0.0, 1.0);
    orientCamera();
    installLights();

    if (isMoving && lowPerfomance)
    {
      glEnable(GL_LIGHTING);
      glCallList(overallLitListFast);
      glDisable(GL_LIGHTING);
      glCallList(overallUnlitListFast);
    } else {
      glEnable(GL_LIGHTING);
      glCallList(overallLitList);
      glDisable(GL_LIGHTING);
      glCallList(overallUnlitList);
    }

    glClear(GL_DEPTH_BUFFER_BIT);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(-viewport_[0] / 20.0, viewport_[0] / 20.0, -viewport_[1] / 20.0, viewport_[1] / 20.0, distances_[0], distances_[1] );
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef( jitterValues[antialising - 1][jitter][0] / 10.0 , jitterValues[antialising - 1][jitter][0] / 10.0 , 0.0);
    const GLfloat a[] = {0.0, 0.0, 0.0, 1.0 };
    glLightfv(GL_LIGHT0, GL_POSITION, a);
    drawNavCube();
    glAccum(GL_ACCUM, 1.0 / antialising);
  }
  glAccum(GL_RETURN, 1.0);
}

void GraphicsWidget::keyReleaseEvent(QKeyEvent *event)
{
  bool needUpdate = false;
  // working with single keypresses
  if ( event->count() == 1 && event->modifiers() == Qt::NoModifier )
  {
    // processing moove events
    if (viewMode == FreeLook)
    {
      clock_t now = clock();
      Vector3 minusZ(-sin(cameraFi) * sin(cameraTheta), -cos(cameraFi) * sin(cameraTheta), -cos(cameraTheta) );
      if ( (event->key() == Qt::Key_Up || event->key() == Qt::Key_W) && isMoving)
      {
        cameraCoord += minusZ * spd * (now - moveTimer) / CLOCKS_PER_SEC;
        needUpdate = true;
      }
      if ( (event->key() == Qt::Key_Down || event->key() == Qt::Key_S) && isMoving )
      {
        cameraCoord -= minusZ * spd * (now - moveTimer) / CLOCKS_PER_SEC;
        needUpdate = true;
      }
      if ( (event->key() == Qt::Key_Left || event->key() == Qt::Key_A) && isMoving )
      {
        Vector3 a = minusZ.prod(Vector3(0.0, 0.0, 1.0)) * spd * (now - moveTimer) / CLOCKS_PER_SEC;
        cameraCoord += a; lookingAt += a;
        needUpdate = true;
      }
      if ( (event->key() == Qt::Key_Right || event->key() == Qt::Key_D) && isMoving )
      {
        Vector3 a = minusZ.prod(Vector3(0.0, 0.0, 1.0)) * spd * (now - moveTimer) / CLOCKS_PER_SEC;
        cameraCoord -= a; lookingAt -= a;
        needUpdate = true;
      }
      if ( event->key() == Qt::Key_Up || event->key() == Qt::Key_W ||
           event->key() == Qt::Key_Down || event->key() == Qt::Key_S ||
           event->key() == Qt::Key_Left || event->key() == Qt::Key_A ||
           event->key() == Qt::Key_Right || event->key() == Qt::Key_D )
      {
        isMoving = false;
        moveTimer = now;
      }
    }
  }
  QGLWidget::keyReleaseEvent(event);
  if ( needUpdate )
  {
    clock_t fpsTimer = clock();
    clock_t now = fpsTimer;
    updateGL();
    fpsTimer = clock();
    lowPerfomance = ( (fpsTimer - now) / CLOCKS_PER_SEC >= 1.0 / 25.0 ) ? true : false;
  }
}

void GraphicsWidget::keyPressEvent(QKeyEvent *event)
{
  bool needUpdate = false;
  // working with single keypresses
  if ( event->count() == 1 && event->modifiers() == Qt::NoModifier )
  {
    // switching between first person and molecule centerered view
    if ( event->key() == Qt::Key_PageUp )
    {
      viewMode = MoleculeCentered;
      lookingAt = Vector3(0.0, 0.0, 0.0);
      compAngles(lookingAt);
      needUpdate = true;
    }
    if ( event->key() == Qt::Key_PageDown )
    {
      viewMode = FreeLook;
      needUpdate = true;
    }
    // processing moove events
    if (viewMode == FreeLook)
    {
      clock_t now = clock();
      Vector3 minusZ(-sin(cameraFi) * sin(cameraTheta), -cos(cameraFi) * sin(cameraTheta), -cos(cameraTheta) );
      if ( (event->key() == Qt::Key_Up || event->key() == Qt::Key_W) && isMoving)
      {
        cameraCoord += minusZ * spd * (now - moveTimer) / CLOCKS_PER_SEC;
        needUpdate = true;
      }
      if ( (event->key() == Qt::Key_Down || event->key() == Qt::Key_S) && isMoving )
      {
        cameraCoord -= minusZ * spd * (now - moveTimer) / CLOCKS_PER_SEC;
        needUpdate = true;
      }
      if ( (event->key() == Qt::Key_Left || event->key() == Qt::Key_A) && isMoving )
      {
        Vector3 a = minusZ.prod(Vector3(0.0, 0.0, 1.0)) * spd * (now - moveTimer) / CLOCKS_PER_SEC;
        cameraCoord += a; lookingAt += a;
        needUpdate = true;
      }
      if ( (event->key() == Qt::Key_Right || event->key() == Qt::Key_D) && isMoving )
      {
        Vector3 a = minusZ.prod(Vector3(0.0, 0.0, 1.0)) * spd * (now - moveTimer) / CLOCKS_PER_SEC;
        cameraCoord -= a;
        lookingAt -= a;
        needUpdate = true;
      }
      if ( event->key() == Qt::Key_Up || event->key() == Qt::Key_W ||
           event->key() == Qt::Key_Down || event->key() == Qt::Key_S ||
           event->key() == Qt::Key_Left || event->key() == Qt::Key_A ||
           event->key() == Qt::Key_Right || event->key() == Qt::Key_D )
      {
        isMoving = true;
        moveTimer = now;
      }
    }
  }
  QGLWidget::keyPressEvent(event);

  if ( needUpdate )
  {
    clock_t fpsTimer = clock();
    clock_t now = fpsTimer;
    updateGL();
    fpsTimer = clock();
    lowPerfomance = ( (fpsTimer - now) / CLOCKS_PER_SEC >= 1.0 / 25.0 ) ? true : false;
  }
}

void GraphicsWidget::mouseMoveEvent(QMouseEvent *event)
{
  bool mv = isMoving, needUpdate = false;
  isMoving = false;
  if (event->buttons() == ( Qt::RightButton | Qt::LeftButton ) || event->buttons() == ( Qt::RightButton | Qt::LeftButton | Qt::MidButton) || event->buttons() == Qt::RightButton )
  {
    isMoving = true;
    float dx = lastMousePos.x() - event->pos().x(), dy = lastMousePos.y() - event->pos().y();
    switch(viewMode)
    {
    case MoleculeCentered:
      cameraFi += dx / 100.0;
      cameraTheta += dy / 100.0;
      cameraCoord = - calcCameraDirection() * (cameraCoord - lookingAt).length()  + lookingAt;
      break;
    case FreeLook:
      cameraFi += dx / 100.0;
      cameraTheta += dy / 100.0;
      lookingAt = calcCameraDirection();
      break;
    }
    needUpdate = true;
  }
  if (event->buttons() == Qt::MidButton)
  {
    isMoving = true;
    int delta = lastMousePos.x() - event->pos().x();
    float da = (isMoving) ? (float) delta / 100.0 : 0.0;
    cameraPsy += da;
    needUpdate = true;
  }
  lastMousePos = event->pos();
  QGLWidget::mouseMoveEvent(event);
  if ( ( mv && !isMoving ) || needUpdate )
  {
    clock_t fpsTimer = clock();
    clock_t now = fpsTimer;
    updateGL();
    fpsTimer = clock();
    lowPerfomance = ( (fpsTimer - now) / CLOCKS_PER_SEC >= 1.0 / 25.0 ) ? true : false;
  }
}

void GraphicsWidget::compNavCube()
{
  navCube = glGenLists(1);
  glNewList(navCube, GL_COMPILE);
  const Vector3 v[8] =
  {
    Vector3(0.0, 0.0, 0.0), Vector3(0.0, 1.0, 0.0),
    Vector3(1.0, 1.0, 0.0), Vector3(1.0, 0.0, 0.0),
    Vector3(1.0, 0.0, 1.0), Vector3(1.0, 1.0, 1.0),
    Vector3(0.0, 1.0, 1.0), Vector3(0.0, 0.0, 1.0)
  };
  MaterialGL mat(0.6, 0.6, 0.6, 1.0);
  mat.install();
  glBegin(GL_QUADS);
    glNormal3dv( (-v[7]).vertex());  glVertex3dv( v[0].vertex() );
    glNormal3dv( (-v[7]).vertex());  glVertex3dv( v[3].vertex() );
    glNormal3dv( (-v[7]).vertex());  glVertex3dv( v[2].vertex() );
    glNormal3dv( (-v[7]).vertex());  glVertex3dv( v[1].vertex() );
    glNormal3dv( v[7].vertex());  glVertex3dv( v[7].vertex() );
    glNormal3dv( v[7].vertex());  glVertex3dv( v[6].vertex() );
    glNormal3dv( v[7].vertex());  glVertex3dv( v[5].vertex() );
    glNormal3dv( v[7].vertex());  glVertex3dv( v[4].vertex() );
    glNormal3dv( (-v[3]).vertex());  glVertex3dv( v[0].vertex() );
    glNormal3dv( (-v[3]).vertex());  glVertex3dv( v[1].vertex() );
    glNormal3dv( (-v[3]).vertex());  glVertex3dv( v[6].vertex() );
    glNormal3dv( (-v[3]).vertex());  glVertex3dv( v[7].vertex() );
    glNormal3dv( v[3].vertex());  glVertex3dv( v[2].vertex() );
    glNormal3dv( v[3].vertex());  glVertex3dv( v[3].vertex() );
    glNormal3dv( v[3].vertex());  glVertex3dv( v[4].vertex() );
    glNormal3dv( v[3].vertex());  glVertex3dv( v[5].vertex() );
    glNormal3dv( v[1].vertex());  glVertex3dv( v[1].vertex() );
    glNormal3dv( v[1].vertex());  glVertex3dv( v[2].vertex() );
    glNormal3dv( v[1].vertex());  glVertex3dv( v[5].vertex() );
    glNormal3dv( v[1].vertex());  glVertex3dv( v[6].vertex() );
    glNormal3dv( (-v[1]).vertex());  glVertex3dv( v[0].vertex() );
    glNormal3dv( (-v[1]).vertex());  glVertex3dv( v[7].vertex() );
    glNormal3dv( (-v[1]).vertex());  glVertex3dv( v[4].vertex() );
    glNormal3dv( (-v[1]).vertex());  glVertex3dv( v[3].vertex() );
  glEnd();

  glBegin(GL_LINES);
    mat.setMaterial(1.0, 0.0, 0.0, 1.0);
    mat.install();
    glVertex3dv( v[0].vertex() );
    glVertex3dv( (v[3] * 2).vertex() );
    mat.setMaterial(0.0, 1.0, 0.0, 1.0);
    mat.install();
    glVertex3dv( v[0].vertex() );
    glVertex3dv( (v[1] * 2).vertex() );
    mat.setMaterial(0.0, 0.0, 1.0, 1.0);
    mat.install();
    glVertex3dv( v[0].vertex() );
    glVertex3dv( (v[7] * 2).vertex() );
  glEnd();
  glEndList();
}

void GraphicsWidget::drawNavCube()
{
  glEnable(GL_LIGHTING);
  glMatrixMode(GL_MODELVIEW);
  glRotatef(cameraPsy / M_PI * 180.0, 0.0, 0.0, 1.0);
  glRotatef(cameraTheta / M_PI * 180.0, 1.0, 0.0, 0.0);
  glRotatef(cameraFi / M_PI * 180.0, 0.0, 0.0, 1.0);
  glTranslatef(-0.5, -0.5, -0.5);
  Vector3 dir = calcCameraDirection();
  Matrix4 mat;
  mat.loadIdentity();
  mat.rotate(dir, cameraPsy);
  dir *= 20;
  cameraTheta = (cameraTheta >= M_PI) ? cameraTheta - M_PI * 2 : (cameraTheta <= - M_PI) ? cameraTheta + M_PI * 2 : cameraTheta ;
  Vector3 localXaxis = (cameraTheta < 0) ? dir.prod(Vector3(0.0, 0.0, 1.0)).normalize() : dir.prod(Vector3(0.0, 0.0, -1.0)).normalize() ;
  Vector3 localYaxis = dir.prod(localXaxis).normalize();
  dir -= mat * localXaxis * ( viewport_[0] / 20.0 - 2.0 ) ;
  dir += mat * localYaxis * ( viewport_[1] / 20.0 - 2.0 ) ;
  glTranslated( dir[0], dir[1], dir[2] );
  glCallList(navCube);
  QFont font("Arial", 5);
  renderText(2.0, 0.0, 0.0, "x", font);
  renderText(0.0, 2.0, 0.0, "y", font);
  renderText(0.0, 0.0, 2.0, "z", font);
}
void GraphicsWidget::drawSphere(const Vector3& center, const float radius, const Material& mat, const int detail)
{
  ((MaterialGL) ( mat ) ).install();
  glPushMatrix();
  glTranslatef(center[0], center[1], center[2]);
  gluSphere(quadricObject, radius, detail * 4, detail * 4);
  glPopMatrix();
}
void GraphicsWidget::drawCylinder(const Vector3& from, const Vector3& to, const float radiusFrom, const float radiusTo, const Material& mat, const int detail)
{
  ((MaterialGL) ( mat ) ).install();
  glPushMatrix();
  glTranslatef(from[0], from[1], from[2]);
  Vector3 dir = to - from, Zaxis = Vector3(0.0, 0.0, 1.0);
  float theta = acos(dir.dot(Zaxis) / dir.length() );
  Vector3 axis = dir.prod(Zaxis);
  glRotatef(-theta / M_PI * 180.0, axis[0], axis[1], axis[2]);
  gluCylinder(quadricObject, radiusFrom, radiusTo, dir.length(), detail * 2, 1);
  glPopMatrix();
}
void GraphicsWidget::drawCone(const Vector3& from, const Vector3& to, const float radius, const Material& mat, const int detail)
{
  ((MaterialGL) ( mat ) ).install();
  glPushMatrix();
  glTranslatef(from[0], from[1], from[2]);
  Vector3 dir = to - from, Zaxis = Vector3(0.0, 0.0, 1.0);
  float theta = acos(dir.dot(Zaxis) / dir.length() );
  Vector3 axis = dir.prod(Zaxis), side = Vector3(radius, 0.0, 0.0 ) - to;
  glRotatef(-theta / M_PI * 180.0, axis[0], axis[1], axis[2]);
  /*
  int n = detail * 2;
  float da = 360.0 / (float) n / 2.0;
  float alpha = M_PI / 2 - acos(Vector3(1.0, 0.0, 0.0).dot(side) / side.length() );
  Vector3 normal(cos(alpha), 0.0, sin(alpha)), top_(0.0, 0.0, dir.length()), point(radiusBase, 0.0, 0.0);
  qDebug() << n << " " << da << " " << alpha << " " << normal << " " << point << " " << top_;
  glBegin(GL_TRIANGLES);
  for (int i = 0; i < n; ++i)
  {
    glNormal3dv(normal.vertex()); glVertex3dv(point.vertex());
    glRotatef(da, 0.0, 0.0, 1.0);
    glNormal3dv(normal.vertex()); glVertex3dv(top_.vertex());
    glRotatef(da, 0.0, 0.0, 1.0);
    glNormal3dv(normal.vertex()); glVertex3dv(point.vertex());
  }
  glEnd(); */
  gluCylinder(quadricObject, radius, 0.0, dir.length(), detail * 2, 1);
  glPopMatrix();
}
void GraphicsWidget::drawParallelepiped(const Vector3& vertex, const Vector3& a, const Vector3& b, const Vector3& c, const Material& mat )
{
  // calculating normals
  Vector3 ab = a.prod(b).normalize(), bc = b.prod(c).normalize(), ac = a.prod(c).normalize();
  Vector3 zero(0.0, 0.0, 0.0), a_b = a+b, a_c = a+c, b_c = b+c, a_b_c = a+b+c;
  if ( acos( ab.dot(c) / c.length() / ab.length() ) < M_PI / 2 ) ab = -ab;
  if ( acos( bc.dot(a) / a.length() / bc.length() ) < M_PI / 2 ) bc = -bc;
  if ( acos( ac.dot(b) / b.length() / ac.length() ) < M_PI / 2 ) ac = -ac;
  Vector3 _ab = -ab, _bc = -bc, _ac = -ac;

  ( (MaterialGL) ( mat ) ).install();
  glPushMatrix();
  glTranslatef(vertex[0], vertex[1], vertex[2]);
  glBegin(GL_QUADS);

  glNormal3dv(ab.vertex()); glVertex3dv(zero.vertex());
  glNormal3dv(ab.vertex()); glVertex3dv(a.vertex());
  glNormal3dv(ab.vertex()); glVertex3dv((a_b).vertex());
  glNormal3dv(ab.vertex()); glVertex3dv(b.vertex());

  glNormal3dv(ac.vertex()); glVertex3dv(zero.vertex());
  glNormal3dv(ac.vertex()); glVertex3dv(a.vertex());
  glNormal3dv(ac.vertex()); glVertex3dv((a_c).vertex());
  glNormal3dv(ac.vertex()); glVertex3dv(c.vertex());

  glNormal3dv(bc.vertex()); glVertex3dv(zero.vertex());
  glNormal3dv(bc.vertex()); glVertex3dv(b.vertex());
  glNormal3dv(bc.vertex()); glVertex3dv((b_c).vertex());
  glNormal3dv(bc.vertex()); glVertex3dv(c.vertex());

  glNormal3dv(_ac.vertex()); glVertex3dv(b.vertex());
  glNormal3dv(_ac.vertex()); glVertex3dv(a_b.vertex());
  glNormal3dv(_ac.vertex()); glVertex3dv((a_b_c).vertex());
  glNormal3dv(_ac.vertex()); glVertex3dv(b_c.vertex());

  glNormal3dv(_bc.vertex()); glVertex3dv(a.vertex());
  glNormal3dv(_bc.vertex()); glVertex3dv(a_c.vertex());
  glNormal3dv(_bc.vertex()); glVertex3dv((a_b_c).vertex());
  glNormal3dv(_bc.vertex()); glVertex3dv(a_b.vertex());

  glNormal3dv(_ab.vertex()); glVertex3dv(c.vertex());
  glNormal3dv(_ab.vertex()); glVertex3dv(b_c.vertex());
  glNormal3dv(_ab.vertex()); glVertex3dv((a_b_c).vertex());
  glNormal3dv(_ab.vertex()); glVertex3dv(a_c.vertex());

  glEnd();
  glPopMatrix();
}



